using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security;
using System.Security.Policy;
using Mbs.Log;
using System.Runtime.Remoting;
using System.Threading;

namespace Mbs.Services
{
    [Serializable]
    public class RemoteClassLoader : MarshalByRefObject, IClassLoader
    {
        private static int domainCounter = 0;
        private static int domainCount = 0;

        [NonSerialized]
        private static readonly ILog log = LoggerManager.GetLogger(typeof(RemoteClassLoader));

        private IClassLoader RemoteLoader;
        public AppDomain CurrentDomain { get; private set; }

        public static int DomainCount
        {
            get { return domainCount; }
        }

        public bool IsDefault
        {
            get { return false; }
        }

        public IServiceLocator Locator { get; private set; }

        public RemoteClassLoader() : this(true) { }
        public RemoteClassLoader(bool isCreateContainer)
        {
            AppDomainSetup info = new AppDomainSetup();
            info.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
            info.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AddIns");

            domainCounter++;
            domainCount++;
            string domainName = "AddIn-" + domainCounter.ToString().PadLeft(3, '0');

            info.ShadowCopyFiles = "true";
            info.ShadowCopyDirectories = info.ApplicationBase;
            info.CachePath = GetCachePath(info.ShadowCopyDirectories, domainName);
           

            Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
            if (evidence.Count == 0)
            {
                Zone zone = new Zone(SecurityZone.MyComputer);
                evidence.AddHost(zone);
                Assembly assembly = Assembly.GetExecutingAssembly();
                Url url = new Url(assembly.CodeBase);
                evidence.AddHost(url);
                Hash hash = new Hash(assembly);
                evidence.AddHost(hash);
            }

            CurrentDomain = AppDomain.CreateDomain(domainName, evidence, info);
            

            String name = Assembly.GetExecutingAssembly().GetName().FullName;
            ObjectHandle oh = Activator.CreateInstance(
                CurrentDomain, name,
                typeof(ClassLoader).FullName,
                false, BindingFlags.Default, null, null, null, null, null);

            object obj = oh.Unwrap();
            Type type = obj.GetType();
            RemoteLoader = obj as IClassLoader;

            RemoteLoader.LoadAssemblies(info.PrivateBinPath);
            if(isCreateContainer)
                Locator = RemoteLoader.NewInstance<IServiceLocator>(ServiceLocator.Root.GetType(), ServiceLocator.Root, RemoteLoader);
        }

        public Assembly[] GetAssemblies()
        {
            return RemoteLoader.GetAssemblies();
        }

        public void LoadAssemblies(string directory)
        {
            RemoteLoader.LoadAssemblies(directory);
        }

        public void LoadAssemblies(string directory, bool includeChildDirectory)
        {
            RemoteLoader.LoadAssemblies(directory, includeChildDirectory);
        }

        public void LoadAssemblies(string directory, string filter, bool includeChildDirectory)
        {
            RemoteLoader.LoadAssemblies(directory, filter, includeChildDirectory);
        }

        public Assembly LoadAssembly(string assemblyFile)
        {
            return RemoteLoader.LoadAssembly(assemblyFile);
        }

        public Type LoadType(string assemblyName, string typeName)
        {
            return RemoteLoader.LoadType(assemblyName, typeName);
        }

        public Type LoadType(string fullTypeName)
        {
            return RemoteLoader.LoadType(fullTypeName);
        }

        public object NewInstance(Type type, params object[] args)
        {
            return RemoteLoader.NewInstance(type, args);
        }

        public T NewInstance<T>(Type type, params object[] args)
        {
            return RemoteLoader.NewInstance<T>(type, args);
        }

        public T NewInstance<T>(params object[] args)
        {
            return RemoteLoader.NewInstance<T>(args);
        }

        public object NewInstance(string fullTypeName, params object[] args)
        {
            return RemoteLoader.NewInstance(fullTypeName, args);
        }

        public T NewInstance<T>(string fullTypeName, params object[] args)
        {
            return (T)RemoteLoader.NewInstance(fullTypeName, args);
        }

        public object Invoke(Delegate method, params object[] args)
        {
            return RemoteLoader.Invoke(method, args);
        }

        public void Dispose()
        {
            log.Info("Unloading AppDomain " + CurrentDomain.FriendlyName);

            bool shadowCopy = CurrentDomain.ShadowCopyFiles;
            string cachePath = CurrentDomain.SetupInformation.CachePath;
            string domainName = CurrentDomain.FriendlyName;

            ThreadPool.QueueUserWorkItem((state) =>
            {
                try
                {
                    AppDomain.Unload(CurrentDomain);
                }
                catch (Exception ex)
                {
                    log.Error("Unable to unload AppDomain " + domainName, ex);
                }
                finally
                {
                    if (shadowCopy)
                        DeleteCacheDir(new DirectoryInfo(cachePath));
                }
            });
        }



        #region Helper Methods
        private string GetCachePath(string shadowCopyPath, string domainName)
        {
            int processId = Process.GetCurrentProcess().Id;
            long ticks = DateTime.Now.Ticks;
            string cachePath = Path.Combine(shadowCopyPath, processId.ToString() + "_" + domainName + "_" + ticks.ToString());

            try
            {
                DirectoryInfo dir = new DirectoryInfo(cachePath);
                if (dir.Exists) dir.Delete(true);
            }
            catch (Exception ex)
            {
                throw new ApplicationException(string.Format("Invalid cache path: {0}", cachePath), ex);
            }

            return cachePath;
        }

        private static void DeleteCacheDir(DirectoryInfo cacheDir)
        {

            if (cacheDir.Exists)
            {
                foreach (DirectoryInfo dirInfo in cacheDir.GetDirectories())
                    DeleteCacheDir(dirInfo);

                foreach (FileInfo fileInfo in cacheDir.GetFiles())
                {
                    fileInfo.Attributes = FileAttributes.Normal;
                    try
                    {
                        fileInfo.Delete();
                    }
                    catch (Exception ex)
                    {
                        log.ErrorFormat(string.Format(
                            "Error deleting {0}, {1}", fileInfo.Name, ex.Message));
                    }
                }

                cacheDir.Attributes = FileAttributes.Normal;

                try
                {
                    cacheDir.Delete();
                }
                catch (Exception ex)
                {
                    log.ErrorFormat(string.Format(
                        "Error deleting {0}, {1}", cacheDir.Name, ex.Message));
                }
            }
        }

        private bool IsAddInDomain(AppDomain domain)
        {
            return domain.FriendlyName.StartsWith("AddIn-");
        }


        public static void DeleteShadowCopyPath(string shadowCopyPath)
        {
            if (Directory.Exists(shadowCopyPath))
                Directory.Delete(shadowCopyPath, true);
        }
        #endregion
    }
}
